﻿using System;
using System.Collections.Generic;
using System.Text;

using IM.Interfaces.Protocols;
using ags = agsXMPP;
using agsClient = agsXMPP.protocol.client;
using agsXMPP.protocol.client;
using IM.Interfaces;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using agsXMPP;
using agsXMPP.protocol.iq.vcard;
using IM.DataTypes;

namespace IM.Protocols.XMPP
{
    public class XMPP : IProtocol
    {
        ags.XmppClientConnection xmppCon = new ags.XmppClientConnection();
        Dictionary<User, ags.Jid> userToJid = new Dictionary<User, agsXMPP.Jid>();
        Dictionary<String, User> stringToUser = new Dictionary<String, User>();

        public User[] Users 
        {
            get
            {
                User[] u = new User[this.userToJid.Count];
                this.userToJid.Keys.CopyTo(u, 0);
                return u;
            }
        }
        public String AccountName
        {
            get;
            set;
        }
        private String Username
        {
            get;
            set;
        }
        private String Password
        {
            get;
            set;
        }

        private int Port
        {
            get;
            set;
        }

        public bool Enabled
        {
            get;
            set;
        }

        private String protocolName = "XMPP";
        public String ProtocolName
        {
            get { return protocolName; }
            private set { protocolName = value; }
        }

        private User myUser;


        public XMPP(String accountName)
        {
            this.AccountName = accountName;
            xmppCon.OnLogin += new ags.ObjectHandler(xmppCon_OnLogin);
            xmppCon.OnError += new ags.ErrorHandler(xmppCon_OnError);
            xmppCon.OnMessage += new agsClient.MessageHandler(xmppCon_OnMessage);
            xmppCon.OnClose += new ags.ObjectHandler(xmppCon_OnClose);
            xmppCon.OnRosterItem += new agsXMPP.XmppClientConnection.RosterHandler(xmppCon_OnRosterItem);
            xmppCon.OnRosterStart += new agsXMPP.ObjectHandler(xmppCon_OnRosterStart);
            xmppCon.OnRosterEnd += new agsXMPP.ObjectHandler(xmppCon_OnRosterEnd);
            xmppCon.OnAuthError += xmppConAuthError;
            xmppCon.OnXmppConnectionStateChanged += new ags.XmppConnectionStateHandler(xmppCon_OnXmppConnectionStateChanged);
            xmppCon.OnPresence += new PresenceHandler(xmppCon_OnPresence);
        }

        #region Events

        void VCardRequest(Jid jid)
        {
            VcardIq viq = new VcardIq(IqType.get, new Jid(jid.Bare));
            String packetId = viq.Id;
            this.xmppCon.IqGrabber.SendIq(viq, new IqCB(VcardResult), null);
        }

        private void VcardResult(object sender, IQ iq, object data)
        {
            if (iq.Type == IqType.result)
            {
                Vcard vcard = iq.Vcard;
                if (vcard != null)
                {
                    User user = this.stringToUser[iq.From.Bare];

                    if (!String.IsNullOrEmpty(vcard.Fullname))
                        user.Nickname = vcard.Fullname;

                    user.Birthday = vcard.Birthday;

                    if (!String.IsNullOrEmpty(vcard.Description))
                        user.Description = vcard.Description;
                    
                        Photo photo = vcard.Photo;
                    if (photo != null)
                    {
                        user.Photo = vcard.Photo.Image;
                    }
                }
            }
        }

        void xmppCon_OnPresence(object sender, Presence pres)
        {
            AddToUsers(pres.From);
            
            User user = this.stringToUser[pres.From.Bare];
            user.Status = pres.Status;

            switch (pres.Type)
            {
                case PresenceType.available:
                    user.Avaibility = User.AvaibilityEnum.Online;
                    break;
                case PresenceType.error:
                    break;
                case PresenceType.invisible: user.Avaibility = User.AvaibilityEnum.Invisible;
                    break;
                case PresenceType.probe:
                    break;
                case PresenceType.subscribe: user.Avaibility = User.AvaibilityEnum.Connecting;
                    break;
                case PresenceType.subscribed: user.Avaibility = User.AvaibilityEnum.Connected;
                    break;
                case PresenceType.unavailable: user.Avaibility = User.AvaibilityEnum.Offline;
                    break;
                case PresenceType.unsubscribe: user.Avaibility = User.AvaibilityEnum.Disconecting;
                    break;
                case PresenceType.unsubscribed: user.Avaibility = User.AvaibilityEnum.Disconected;
                    break;
            }

            switch (pres.Show)
            {
                case ShowType.NONE:
                    break;
                case ShowType.away: user.Avaibility = User.AvaibilityEnum.Away;
                    break;
                case ShowType.chat: user.Avaibility = User.AvaibilityEnum.Online;
                    break;
                case ShowType.dnd: user.Avaibility = User.AvaibilityEnum.DND;
                    break;
                case ShowType.xa: user.Avaibility = User.AvaibilityEnum.Online;
                    break;
                default:
                    break;
            }
        }

        void xmppCon_OnXmppConnectionStateChanged(object sender, ags.XmppConnectionState state)
        {
            ConnectionStates myState = ConnectionStates.Disconnected;

            switch (state)
            {
                case agsXMPP.XmppConnectionState.Authenticated: myState = ConnectionStates.Authenticated;
                    break;
                case agsXMPP.XmppConnectionState.Authenticating: myState = ConnectionStates.Authenticating;
                    break;
                case agsXMPP.XmppConnectionState.Binded:
                    break;
                case agsXMPP.XmppConnectionState.Binding:
                    break;
                case agsXMPP.XmppConnectionState.Compressed:
                    break;
                case agsXMPP.XmppConnectionState.Connected: myState = ConnectionStates.Connected;
                    break;
                case agsXMPP.XmppConnectionState.Connecting: myState = ConnectionStates.Connecting;
                    break;
                case agsXMPP.XmppConnectionState.Disconnected: myState = ConnectionStates.Disconnected;
                    break;
                case agsXMPP.XmppConnectionState.Registered:
                    break;
                case agsXMPP.XmppConnectionState.Registering:
                    break;
                case agsXMPP.XmppConnectionState.Securing:
                    break;
                case agsXMPP.XmppConnectionState.SessionStarted:
                    break;
                case agsXMPP.XmppConnectionState.StartCompression:
                    break;
                case agsXMPP.XmppConnectionState.StartSession:
                    break;
                default:
                    break;
            }

            if (OnConnectionStateChanged != null)
                OnConnectionStateChanged(sender, myState);
        }

        void xmppCon_OnRosterStart(object sender)
        {
            userToJid.Clear();
        }

        void xmppCon_OnRosterEnd(object sender)
        {
            if (OnContactListLoad != null)
                OnContactListLoad(this, this.Users);
        }

        void xmppCon_OnRosterItem(object sender, agsXMPP.protocol.iq.roster.RosterItem item)
        {
            AddToUsers(item.Jid);
        }

        private void AddToUsers(ags.Jid jidUser)
        {
            User user = new User(jidUser.Bare, this);

            if (!stringToUser.ContainsKey(jidUser.Bare))
            {
                stringToUser.Add(jidUser.Bare, user);
                user.Nickname = jidUser.ToString();
                VCardRequest(jidUser);
            }

            if (stringToUser.ContainsKey(jidUser.Bare))
            {
                if (!userToJid.ContainsKey(stringToUser[jidUser.Bare]))
                {
                    userToJid.Add(stringToUser[jidUser.Bare], jidUser);
                }
            }
        }

        void xmppCon_OnClose(object sender)
        {
            if (OnClose != null)
                OnClose(this);
        }

        void xmppCon_OnMessage(object sender, agsClient.Message msg)
        {
            if (OnMessage != null)
            {
                OnMessage(this, stringToUser[msg.From.Bare], msg.Body);
            }
        }

        void xmppCon_OnError(object sender, Exception ex)
        {
            if (OnError != null)
                OnError(this, ex);
        }

        void xmppCon_OnLogin(object sender)
        {
            AddToUsers(xmppCon.MyJID);

            this.myUser = this.stringToUser[xmppCon.MyJID.Bare];
            
            VCardRequest(this.xmppCon.MyJID);

            if (OnLogin != null)
                OnLogin(this);
        }
		
        void xmppConAuthError (object sender, agsXMPP.Xml.Dom.Element e)
        {
			// TODO auth error is passed just as an error
        	if (OnError != null)
				OnError(sender, new Exception("Authorization error occured, please review your settings."));
        }
		

        #endregion

        #region IProtocol1 Members

        public event IM.Interfaces.Protocols.ObjectHandler OnLogin;
        public event IM.Interfaces.Protocols.ErrorHandler OnError;
        public event IM.Interfaces.Protocols.MessageHandler OnMessage;
        public event IM.Interfaces.Protocols.ObjectHandler OnClose;
        public event ContactListHandler OnContactListLoad;
        public event ConnectionStateHandler OnConnectionStateChanged;

        public void Login(string username, string password)
        {
            Login(username, password);
        }

        public void Login(string username, string password, int port)
        {
            ags.Jid jid = new ags.Jid(username);

            this.xmppCon.Username = jid.User;
            this.xmppCon.Server = jid.Server;
            this.xmppCon.Port = port;
            this.xmppCon.Password = password;
            this.xmppCon.AutoResolveConnectServer = true;
			this.xmppCon.Open();
        }

        public void Logout()
        {
            xmppCon.Close();

            if (OnClose != null)
                OnClose(this);
        }

        public void SendMessage(string message, User username)
        {
            agsClient.Message msg = new agsClient.Message();
            msg.Type = agsClient.MessageType.chat;
            msg.To = new ags.Jid(username.Username);
            msg.Body = message;

            xmppCon.Send(msg);
        }

        public User UserID
        {
            get 
            {
                return this.myUser;
            }
        }

        #endregion

        public Object GetSettingsData()
        {
            SettingsData data = new SettingsData(this.Username, this.Password, this.Port.ToString());
            data.InstanceName = this.AccountName;
            data.Enabled = this.Enabled;
            data.Type = this.ProtocolName;

            return data as Object;
        }

        public void LoadSettingsData(object obj)
        {
            SettingsData data = obj as SettingsData;

            if (data.Enabled)
                this.Login(data.Login, data.Password, data.Port);

            this.Username = data.Login;
            this.Password = data.Password;
            this.Port = data.Port;
            this.Enabled = data.Enabled;
            this.ProtocolName = data.Type;
            this.AccountName = data.InstanceName;
        }
    }
}
